/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.web.jitengine.dynamicform.htmltemplate;

import com.inspur.edp.web.common.serialize.SerializeUtility;
import com.inspur.edp.web.common.utility.StringUtility;
import com.inspur.edp.web.formmetadata.i18n.component.ComponentUtility;
import com.inspur.edp.web.formmetadata.i18n.constant.ComponentType;
import com.inspur.edp.web.formmetadata.metadata.formdom.FormDOM;
import com.inspur.edp.web.jitengine.dynamicform.htmltemplate.htmltemplateextractor.HtmlTemplateManager;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;

/**
 * 解析表单--提取html模板
 *
 * @author guozhiqi
 */
public class HtmlTemplateGenerator {
    public static HtmlTemplateGenerator init() {
        return new HtmlTemplateGenerator();
    }

    public List<HtmlTemplateEntity> generate(FormDOM formDOM) {
        List<HtmlTemplateEntity> htmlTemplateEntityList = new ArrayList<>();
        ArrayList<HashMap<String, Object>> components = formDOM.getModule().getComponents();
        // 递归提取每个component中的组件及其对应的待国际化的值
        for (HashMap<String, Object> component : components) {
            // 每个component是一个树结构
            // 表单树表现出浅而宽的特性，所以优先使用深度优先算法
            if (component != null) {
                DepthFirstSearchTraverse(htmlTemplateEntityList, component, formDOM);
            }
        }
        return htmlTemplateEntityList;
    }

    /**
     * 深度优先搜索遍历(DFS)
     *
     * @param htmlTemplateEntityList 资源项列表
     * @param component              表单component
     * @return
     */
    public ArrayList<Object> DepthFirstSearchTraverse(List<HtmlTemplateEntity> htmlTemplateEntityList, HashMap<String, Object> component, FormDOM formDOM) {
        ArrayList<Object> list = new ArrayList<>();

        Stack<HashMap<String, Object>> componentStack = new Stack<>();
        // 将当前节点入栈
        componentStack.push(component);
        while (componentStack.size() != 0) {
            // 节点出栈
            HashMap<String, Object> currentComponent = componentStack.pop();
            if (currentComponent == null) {
                break;
            }

            String componentType = ComponentUtility.getInstance().getType(currentComponent);
            // 处理当前出栈的节点  提取对应的资源项
            HandlePoppedNode(htmlTemplateEntityList, currentComponent, formDOM);

            // 兄弟节点压栈
            AppendSiblingNode(componentStack, currentComponent);

            // 子节点压栈
            // 由于checkGroup中包含items属性且不包含具体的类型 所以针对checkGroup 不读取其items
            if (!StringUtility.isNullOrEmpty(componentType) &&
                    !"CheckGroup".equals(componentType)) {
                AppendChildrenNode(componentStack, currentComponent);
            }
        }

        return list;
    }

    /**
     * 提取当前节点的国际化翻译项
     *
     * @param htmlTemplateEntityList 国际化资源项
     * @param currentComponent       当前待处理的节点
     */
    private void HandlePoppedNode(List<HtmlTemplateEntity> htmlTemplateEntityList, HashMap<String, Object> currentComponent, FormDOM formDOM) {
        List<HtmlTemplateEntity> extractHtmlTemplateEntityList = HtmlTemplateManager.extract(currentComponent);
        if (extractHtmlTemplateEntityList != null && !extractHtmlTemplateEntityList.isEmpty()) {
            htmlTemplateEntityList.addAll(extractHtmlTemplateEntityList);
        }
    }

    /**
     * 追加兄弟节点：特殊处理非子组件属性中。如tabPage中的toolbar
     * {"contents":{},"toolbar":{}, "editor":{}}
     *
     * @param componentStack
     * @param currentComponent
     */
    private static void AppendSiblingNode(Stack<HashMap<String, Object>> componentStack, HashMap<String, Object> currentComponent) {
        String componentType = ComponentUtility.getInstance().getType(currentComponent);
        if (!StringUtility.isNullOrEmpty(componentType) && componentType.equals(ComponentType.TabPage)) {
            HashMap<String, Object> toolbarObject = ComponentUtility.getInstance().GetToolbar(currentComponent);
            if (toolbarObject != null) {
                componentStack.push(toolbarObject);
            }
        }
    }

    /**
     * 子节点入栈
     *
     * @param componentStack
     * @param currentComponent
     */
    private void AppendChildrenNode(Stack<HashMap<String, Object>> componentStack, HashMap<String, Object> currentComponent) {
        if (currentComponent == null || currentComponent.isEmpty()) {
            return;
        }
        ArrayList<HashMap<String, Object>> childComponentCollection = GetChildComponents(currentComponent);
        for (int i = childComponentCollection.size() - 1; i >= 0; i--) {
            HashMap<String, Object> childComponent = childComponentCollection.get(i);
            if (childComponent != null) {
                componentStack.push(childComponent);
            }
        }
    }

    /**
     * 获取子组件列表
     *
     * @param component
     * @return
     */
    private ArrayList<HashMap<String, Object>> GetChildComponents(HashMap<String, Object> component) {
        ArrayList<HashMap<String, Object>> childCompnentCollection = new ArrayList<>();

        // 获取组件属性中包含的子组件
        ArrayList<HashMap<String, Object>> childAttribtueCompnentCollection = GetAttribtueChildComponents(component);
        if (childAttribtueCompnentCollection != null && !childAttribtueCompnentCollection.isEmpty()) {
            childCompnentCollection.addAll(childAttribtueCompnentCollection);
        }

        Object contents = component.get("contents");
        if (contents == null) {
            // 尝试从items中获取子节点信息（兼容使用items属性存储子节点的场景，如ToolBar）
            contents = component.get("items");
            if (contents == null) {
                // 兼容datagrid
                contents = component.get("fields");
            }
            if (contents == null) {
                return childCompnentCollection;
            }
        }

        // 如何从object转换成List<Dictionary<string, object>>
        // https://stackoverflow.com/questions/632570/cast-received-object-to-a-listobject-or-ienumerableobject
        ArrayList<Object> contentsCollection = new ArrayList<>((List<Object>) contents);
        for (Object localComponent : contentsCollection) {
            // 从object转换成Dictionary<string, object>
            HashMap<String, Object> convertObject = ConvertToDictionaryObject(localComponent);
            childCompnentCollection.add(convertObject);
        }

        return childCompnentCollection;
    }

    /**
     * 侧边栏或tabPage的工具栏
     *
     * @param component
     * @return
     */
    private ArrayList<HashMap<String, Object>> GetAttribtueChildComponents(HashMap<String, Object> component) {
        ArrayList<HashMap<String, Object>> childAttribtueCompnentCollection = new ArrayList<>();
        // 侧边栏 或tab的工具栏
        if (ComponentUtility.getInstance().getType(component).equals(ComponentType.SIDEBAR)) {
            HashMap<String, Object> toolbarAttributeObject = ComponentUtility.getInstance().GetToolbar(component);
            // 仅在存在toolbar时处理
            if (toolbarAttributeObject != null) {
                childAttribtueCompnentCollection.addAll(ComponentUtility.getInstance().GetItems(toolbarAttributeObject));
            }
            return childAttribtueCompnentCollection;
        }


        return null;
    }

    // 如何从object转换成Dictionary<string, object>
    // https://stackoverflow.com/questions/11576886/how-to-convert-object-to-dictionarytkey-tvalue-in-c
    private static HashMap<String, Object> ConvertToDictionaryObject(Object targetObject) {
        String json = SerializeUtility.getInstance().serialize(targetObject, false);
        return SerializeUtility.getInstance().deserialize(json, HashMap.class);
    }
}
